﻿using AuSys.Database.Context;
using AuSys.Models.Files;
using AuSys.Services.App.FileStorage;
using AuSys.Services.App.UserInfo.Abstractions;
using AuSys.Utils.Cryptography;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using FluentValidation;
using System.Collections.Concurrent;
using RqEx = AuSys.Utils.Exceptions.RequestInvalidException;

namespace AuSys.Repos.Files
{
    public class FileItemRepo(
        DataContext dataContext,
        IFileStorage fileStorage,
        IConfiguration configuration,
        IMapper mapper,
        IUserIdProvider httpUserIdProvider
        ) : RepoCache<FileItem, FileItemCache>(dataContext)
    {
        private readonly static ConcurrentDictionary<int, Lock> perUserLock = [];
        //TODO：鲁棒性（相同Hash的文件改为指向同一NameStore，并删除重复的文件）
        //TODO：清理未引用文件（IFileStorage提供遍历文件功能）
        public int TryCreateFileItem(Stream stream, string fileName)
        {
            var uid = httpUserIdProvider.GetUserId();
            if (uid == 0)
                throw new RqEx("获取用户id失败");
            Lock lk = perUserLock.GetOrAdd(uid, new Lock());
            lock (lk)
            {
                //获取文件后缀名
                var ext = Path.GetExtension(fileName);
                if (string.IsNullOrWhiteSpace(ext))
                    throw new RqEx("上传文件缺少后缀名");
                //由于Path.GetRandomFileName返回的文件名（不含后缀）长度为8
                if (ext.Length > FileItem.nameStoreMaxLength - 8)
                    throw new RqEx("上传文件后缀名过长");

                //将stream写入临时文件
                var tempFileName = GetTempFileName();
                using var tempFileWrite = File.Create(tempFileName);
                {
                    stream.CopyTo(tempFileWrite);
                    stream.Flush();
                    tempFileWrite.Flush();
                    stream.Close();
                    tempFileWrite.Close();
                }

                if (!int.TryParse(configuration["FileStorage:MaxLength"], out int maxFileLength))
                    maxFileLength = 10 * 1000 * 1000;

                //获取文件尺寸
                var fileLength = new FileInfo(tempFileName).Length;
                if (fileLength > maxFileLength)
                    throw new RqEx("文件过大");
                var fileLengthInt = (int)fileLength;

                using var tempFileRead = File.OpenRead(tempFileName);
                void cleanUp()
                {
                    tempFileRead.Close();
                    File.Delete(tempFileName);
                }
                //使用临时文件计算md5并查找是否有Hash相同的FileItem
                var md5 = tempFileRead.GetMD5();
                var exitsingId = Existing
                    .Where(x => x.Hash == md5)
                    .Select(x => x.Id)
                    .FirstOrDefault();
                //若存在，删除临时文件并直接返回Id
                if (exitsingId > 0)
                {
                    cleanUp();
                    return exitsingId;
                }
                var newFileStoreName = Path.GetRandomFileName();
                newFileStoreName = Path.ChangeExtension(newFileStoreName, ext);
                if (newFileStoreName.Length > FileItem.nameStoreMaxLength)
                    throw new Exception("文件存储名过长");
                tempFileRead.Seek(0, SeekOrigin.Begin);
                if (!fileStorage.Save(tempFileRead, newFileStoreName, out string? errmsg))
                {
                    cleanUp();
                    throw new RqEx(errmsg);
                }
                FileItem newItem = new()
                {
                    NameStore = newFileStoreName,
                    Hash = md5,
                    Length = fileLengthInt,
                    UploaderId = httpUserIdProvider.GetUserId()
                };
                int id = Add(newItem);
                cleanUp();
                return id;
            }
        }

        private const string tempPathConfigKey = "TempPath";
        private string GetTempFileName()
        {
            var tempDirPath = configuration[tempPathConfigKey]
                ?? throw new Exception($"缺少配置项<{tempPathConfigKey}>");
            var tempDir = new DirectoryInfo(tempDirPath);
            if (!tempDir.Exists)
                tempDir.Create();
            return Path.Combine(tempDirPath, Path.GetRandomFileName());
        }

        public FileItemCache? GetFileItemCache(int id)
            => CacheSearchSingle(x => x.Id == id);
        protected override IQueryable<FileItemCache> DbProjectToCache(IQueryable<FileItem> source)
            => source.ProjectTo<FileItemCache>(mapper.ConfigurationProvider);
    }

    public class FileItemCache
    {
        public int Id { get; set; }
        public string? NameStore { get; set; }
    }
    public class FileItemDto
    {
        public int Id { get; set; }
        public string? NameDisplay { get; set; }
        public string? NameStore { get; set; }
        public int Length { get; set; }
        public string? Hash { get; set; }
        public int OwnerId { get; set; }
        public string? OwnerName { get; set; }
        public string? LastUpdate { get; set; }
    }
    public class FileItemDtoValidator : AbstractValidator<FileItem>
    {
        public FileItemDtoValidator()
        {
            ClassLevelCascadeMode = CascadeMode.Stop;
        }
    }
    public class FileItemProfile : Profile
    {
        public FileItemProfile()
        {
            CreateMap<FileItem, FileItemDto>();
            CreateMap<FileItemDto, FileItem>();
            CreateMap<FileItem, FileItemCache>();
        }
    }
}
